﻿#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
using namespace std;

class Person {
public:
	Person(const char* name = "peter")
		: _name(name) {
		cout << "Person()" << endl;
	}

	Person(const Person& p)
		: _name(p._name) {
		cout << "Person(const Person& p)" << endl;
	}

	Person& operator=(const Person& p) {
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}

	~Person() { cout << "~Person()" << endl; }
protected:
	string _name;//姓名
};

class Student : public Person {
public:
	//构造函数
	//如果子类[没有]显式定义构造函数，会自动调用父类的默认构造函数
	//如果子类显示定义构造函数，要把父类当成一个完整对象去初始化
	Student(const char* name, int num)
		//:_name(name) 该方式错误
		: Person(name) //用一个像匿名对象的方式调用父类构造函数
		, _num(num) {
		cout << "Student()" << endl;
	}
	//1.父类[没有]默认构造函数 → 必须显式调用父类的某个构造函数。
	//2.父类有默认构造函数 → 可以不显式调用（编译器自动调用），但如果你想用特定值初始化父类成员，仍然需要显式调用。
	//3.调用方式：在子类构造函数的初始化列表中，像“匿名对象”一样调用父类构造函数

	//拷贝构造
	//如果子类[没有]显式定义拷贝构造函数，会自动调用父类的拷贝构造函数
	Student(const Student& s)
		: Person(s)//子类赋值给父类的引用，触发切片。只赋值了子类中父类的那部分
		, _num(s._num) {
		cout << "Student(const Student& s)" << endl;
	}
	//1.如果父类有默认拷贝构造函数（即没有显式定义，且所有成员都可拷贝），
	//子类可以不显式调用父类拷贝构造，编译器会自动调用父类的默认拷贝构造。
	//2.如果父类没有默认拷贝构造函数（例如，父类显式定义了拷贝构造但未提供默认版本，或者某些成员不可拷贝），
	//子类必须在初始化列表中显式调用父类的拷贝构造函数，否则编译错误。

	//赋值重载
	//如果子类[没有]显式定义赋值重载，会自动调用父类的赋值重载
	Student& operator=(const Student& s) {
		cout << "Student& operator= (const Student& s)" << endl;
		if (this != &s) {
			//operator=(s);//错误，栈溢出。父类和子类的赋值构成隐藏，这种方式是调用子类的赋值重载
			Person::operator=(s);
			_num = s._num;
		}
		return *this;
	}

	//由于多态的原因，析构函数会被统一处理成destructor
	//子类和父类的析构构成隐藏
	~Student() {
		//~Person();//错误，构成隐藏，所以这种方式是访问子类的析构

		//Person::~Person();//显示调用的方式。而main函数中只有3个对象，但每个对象Person的析构都调用了2次
		//子类中父类是先声明的，所以先构造父类。而析构反过来要先析构子类。
		//但自己不一定能保证这个析构顺序，所以不能显示调用父类析构函数。
		//子类析构函数结束时会自动调用父类析构函数

		//ChatGPT
		//C++ 规定，析构函数的调用顺序与构造顺序相反：
		//1.先执行子类的析构函数体（~Derived() 的代码）。
		//2.再自动调用父类的析构函数（~Base()）。
		//3.最后析构成员变量（按声明逆序析构）。
		//这种顺序是 编译器强制保证 的，无需程序员干预。析构必须保证逆向安全销毁
		//析构顺序必须严格反向（先子类后父类），如果允许手动调用父类析构：
		//可能导致 重复析构（如果编译器之后又自动调用一次）。
		//破坏对象析构的安全性（如父类资源被子类依赖时提前释放）。

		cout << "~Student()" << endl;
	}
protected:
	int _num;//学号
};

int main() {
	Student s1("jack", 18);
	Student s2(s1);
	Student s3("rose", 17);
	s1 = s3;

	return 0;
}